﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SDCloudSolution.SDCloud.BusinessLayer.Model.Interfaces;
using SDCloudSolution.SDCloud.Entities;
using SDCloudSolution.SDCloud.Entities.Models;
using SDCloudSolution.SDCloud.Web.Infrastructure.Interfaces;
using SDCloudSolution.SDCloud.Web.Models;
using SDCloudSolution.SDCloud.Web.Models.ModelRun;

namespace SDCloudSolution.SDCloud.Web.Infrastructure
{
    public class ModelRunViewModelProvider : IModelRunViewModelProvider
    {
        private readonly IModelRunComponent _modelRunComponent;
        private readonly IModelComponent _modelComponent;
        private readonly IUserIdentityProvider _identityProvider;
        private readonly IDateTimeFormatter _dateTimeFormatter;

        public ModelRunViewModelProvider(
            IModelRunComponent modelRunComponent, 
            IModelComponent modelComponent,
            IUserIdentityProvider identityProvider, 
            IDateTimeFormatter dateTimeFormatter)
        {
            _modelRunComponent = modelRunComponent;
            _modelComponent = modelComponent;
            _identityProvider = identityProvider;
            _dateTimeFormatter = dateTimeFormatter;
        }

        public async Task<ModelRunListViewModel> GetListAsync(PagingModel pagingModel)
        {
            var models = await _modelComponent.FindAllModelsForUser(_identityProvider.Current.UserId);

            var modelIds = models.Select(model => model.Id);

            var paging = new Paging(pagingModel.PageIndex, pagingModel.PageSize);

            var modelRunsCollection = await _modelRunComponent.FindByModelIdsAsync(modelIds, paging);

            return ConvertToViewModel(modelRunsCollection, paging, models);
        }

        public async Task<ModelRunListViewModel> GetActiveListAsync(PagingModel pagingModel)
        {
            var models = await _modelComponent.FindAllModelsForUser(_identityProvider.Current.UserId);
            var modelIds = models.Select(model => model.Id);

            var paging = new Paging(pagingModel.PageIndex, pagingModel.PageSize);

            var statuses = new[]
            {
                ModelRunStatus.Queued,
                ModelRunStatus.Waiting,
                ModelRunStatus.InProgress
            };

            var modelRunsCollection = await _modelRunComponent.FindByModelIdsAndStatusesAsync(modelIds, statuses, paging);

            return ConvertToViewModel(modelRunsCollection, paging, models);
        }

        public async Task<SpecificModelRunListViewModel> GetListFromModelAsync(int modelId, PagingModel pagingModel)
        {
            var model = await _modelComponent.GetModelByIdAsync(modelId);

            if (model == null || model.UserId != _identityProvider.Current.UserId)
            {
                throw new UnauthorizedAccessException();
            }

            var modelIds = new[] { model.Id };

            var paging = new Paging(pagingModel.PageIndex, pagingModel.PageSize);

            var modelRunsCollection = await _modelRunComponent.FindByModelIdsAsync(modelIds, paging);

            return ConvertToViewModel(modelRunsCollection, paging, model);
        }

        public async Task<ModelRunListItemViewModel> GetLastModelRunForModelAsync(int modelId)
        {
            var entity = await _modelRunComponent.FindLastModelRunAsync(modelId);

            return entity != null
                ? ConvertToListItemViewModel(entity, null)
                : null;
        }

        public async Task QuickStartForModelAsync(int modelId)
        {
            var lastModelRun = await GetLastModelRunForModelAsync(modelId);
            if (lastModelRun != null && !lastModelRun.IsCompleted)
            {
                throw new InvalidOperationException("Can not start model run");
            }

            var modelRunEntity = new ModelRunEntity
            {
                ModelId = modelId,
                Status = ModelRunStatus.Queued,
                Priority = ModelRunPriority.Normal
            };

            await _modelRunComponent.InsertModelRunAsync(modelRunEntity);
        }

        public async Task<ModelRunViewModel> GetModelRunViewModelAsync(int modelRunId)
        {
            var modelRunEntity = await _modelRunComponent.GetByIdAsync(modelRunId);

            var modelEntity = await _modelComponent.GetModelByIdAsync(modelRunEntity.ModelId);

            return ConvertToViewModel(modelRunEntity, modelEntity);
        }

        public async Task CancelModelRunAsync(int modelRunId)
        {
            var modelRunEntity = await _modelRunComponent.GetByIdAsync(modelRunId);
            modelRunEntity.Status = ModelRunStatus.Canceled;
            modelRunEntity.EndDateUtc = DateTime.UtcNow;
            await _modelRunComponent.UpdateModelRunAsync(modelRunEntity);
        }

        private SpecificModelRunListViewModel ConvertToViewModel(EntityCollection<ModelRunEntity> collection, Paging paging, ModelEntity model)
        {
            return new SpecificModelRunListViewModel
            {
                ModelId = model.Id,
                ModelName = model.Name,
                Items = collection.EntityList.Select(entity => ConvertToListItemViewModel(entity, model)).ToList(),
                Paging = new PagingViewModel
                {
                    PageIndex = paging.PageIndex,
                    PageSize = paging.PageSize,
                    TotalCount = collection.TotalCount
                }
            };
        }

        private ModelRunListViewModel ConvertToViewModel(EntityCollection<ModelRunEntity> collection, Paging paging, IEnumerable<ModelEntity> models)
        {
            return new ModelRunListViewModel
            {
                Items = collection.EntityList == null
                    ? new List<ModelRunListItemViewModel>()
                    : collection.EntityList.Select(entity =>
                        ConvertToListItemViewModel(
                            entity, 
                            models != null 
                                ? models.FirstOrDefault(model => model.Id == entity.ModelId)
                                : null)).ToList(),
                Paging = new PagingViewModel
                {
                    PageIndex = paging.PageIndex,
                    PageSize = paging.PageSize,
                    TotalCount = collection.TotalCount
                }
            };
        }

        private ModelRunListItemViewModel ConvertToListItemViewModel(ModelRunEntity modelRunEntity, ModelEntity modelEntity)
        {
            return new ModelRunListItemViewModel
            {
                ModelRunId = modelRunEntity.Id,
                ModelId = modelRunEntity.ModelId,
                ModelName = modelEntity != null
                    ? modelEntity.Name 
                    : string.Empty,
                QuequedDate = _dateTimeFormatter.ToFullDateTimeFormat(modelRunEntity.QueuedDateUtc),
                ElapsedTime = GetElapsedTimeString(modelRunEntity),
                StatusTitle = ConvertStatusToText(modelRunEntity.Status),
                StatusType = ConvertToViewModel(modelRunEntity.Status),
                IsCompleted = IsCompleted(modelRunEntity)
            };
        }

        private ModelRunViewModel ConvertToViewModel(ModelRunEntity modelRunEntity, ModelEntity modelEntity)
        {
            return new ModelRunViewModel
            {
                ModelRunId = modelRunEntity.Id,
                ModelId = modelRunEntity.ModelId,
                ModelName = modelEntity != null
                    ? modelEntity.Name
                    : string.Empty,
                QuequedDate = _dateTimeFormatter.ToUserFriendlyDateFormat(modelRunEntity.QueuedDateUtc),
                Duration = GetElapsedTimeString(modelRunEntity),
                StatusType = ConvertToViewModel(modelRunEntity.Status),
                StatusTitle = ConvertStatusToText(modelRunEntity.Status),
                CanStop = CanStopModelRun(modelRunEntity)
            };
        }

        private bool IsCompleted(ModelRunEntity entity)
        {
            switch(entity.Status)
            {
                case ModelRunStatus.Canceled:
                case ModelRunStatus.Error:
                case ModelRunStatus.Warning:
                case ModelRunStatus.Success:
                    return true;
                default:
                    return false;
            }
        }

        private bool CanStopModelRun(ModelRunEntity entity)
        {
            return entity.Status == ModelRunStatus.Queued ||
                   entity.Status == ModelRunStatus.Waiting ||
                   entity.Status == ModelRunStatus.InProgress;
        }

        private string ConvertStatusToText(ModelRunStatus status)
        {
            switch (status)
            {
                case ModelRunStatus.Queued:
                case ModelRunStatus.Waiting:
                    return "Queued";
                case ModelRunStatus.InProgress:
                    return "In progress";
                case ModelRunStatus.Success:
                    return "Success";
                case ModelRunStatus.Warning:
                    return "Warning";
                case ModelRunStatus.Error:
                    return "Error";
                case ModelRunStatus.Canceled:
                    return "Canceled";
            }

            return "n/a";
        }

        private StatusViewModel ConvertToViewModel(ModelRunStatus status)
        {
            switch (status)
            {
                case ModelRunStatus.Warning:
                case ModelRunStatus.Queued:
                case ModelRunStatus.Waiting:
                    return StatusViewModel.Warning;
                case ModelRunStatus.InProgress:
                    return StatusViewModel.Primary;
                case ModelRunStatus.Success:
                    return StatusViewModel.Success;
                case ModelRunStatus.Canceled:
                case ModelRunStatus.Error:
                    return StatusViewModel.Danger;
            }

            return StatusViewModel.Default;
        }

        private string GetElapsedTimeString(ModelRunEntity entity)
        {
            if (!entity.StartDateUtc.HasValue)
            {
                return "Not yet started";
            }

            var endTime = entity.EndDateUtc.HasValue
                ? entity.EndDateUtc.Value
                : DateTime.UtcNow;

            var deltaTime = endTime - entity.StartDateUtc.Value;

            return _dateTimeFormatter.ToUserFriendlyTimeFormat(deltaTime);
        }
    }
}